<?php
/**
  * Musashi - A project manager for music groups and bands
  * Copyright (C) 2010 Daniel Torres
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  */

/**
 *  Controls Musashi's file system operations
 *  
 * @package com_musashi
 * @author Daniel Torres
 */
 
// Make sure we have access to this page
defined('_JEXEC') or die ('Restricted access');

jimport('joomla.application.component.model');

/**
 *  
 *  Model for file related operations
 *  @author Daniel Torres
 */
 class MusashiModelFiles extends JModel
 {
    var $_musashiMemberID = null;       // The Musashi ID of the current member (as specified on the Member table)
    var $_musashiUsrDir   = "usrdata";  // Folder where we store user-provided files
    
   /**
     *  Obtains the member id of the current user. This function could be placed
     *  somewhere else (like the user module) and just passed to all other models,
     *  but for the sake of simplicity (and perhaps bad design) I'm just repeating it.
     *  
     *  WARNING: It is assumed that we have a member entry!
     */
    function getMemberID()
    {
        if( $this->_musashiMemberID == null )
        {
            // Get the current user
            $user =& JFactory::getUser();
        
            // See if we have him/her
            $query = "SELECT id FROM #__Musashi_Member WHERE userId =".$user->id;
            $this->_db->setQuery($query);
            $result = $this->_db->loadObject();
            
            if( $result != null ){
                $this->_musashiMemberID = $result->id;
            }

        }
        
        return $this->_musashiMemberID;
    }
    
    /**
     *  Gets you the file upload path
     */
    function getUploadPath()
    {
        return JPATH_COMPONENT.DS.$this->_musashiUsrDir.DS;
    }
    
    /**
     *  Adds a folder with the provided name and description. If a folder already
     *  exists, the function simply succeeds quietly
     *  @param a_sName Name of the new folder
     *  @param a_sDesc Description of the new folder
     */
    function addFolder( $a_sName, $a_sDesc )
    {
        // Make sure we don't have a folder with this name already
        $sql = 'SELECT id FROM #__Musashi_Folder WHERE name = "'.$a_sName.'"';
        $this->_db->setQuery($query);
        
        if( $this->_db->loadObject() == null )
        {
            // Add the new folder
            $sql = 'INSERT INTO #__Musashi_Folder (name,description) VALUES ("'.$a_sName.'","'.$a_sDesc.'")';
            $this->_db->setQuery($sql);            
            return $this->_db->query();
                        
        }
        
        return true;
    }
    
    /**
     *  Returns the ID of the folder with the provided name. If the folder is
     *  not found, returns NONE
     *  @param a_sName Name of the folder
     */
    function getFolderID($a_sName)
    {
        $sql = "SELECT id FROM #__Musashi_Folder WHERE name = '".$a_sName."'";
        $this->_db->setQuery($sql);
        return $this->_db->loadResult();        
    }
        
    
    /**
     *  Makes sure that the root folder exists
     */
    function verifyRootFolder()
    {
        $sql = 'SELECT id FROM #__Musashi_Folder WHERE name="root"';
        $this->_db->setQuery($sql);            
        $rootFolder = $this->_db->loadResult();
        
        if( $rootFolder == null )
        {
            $this->addFolder('root','default document location');
        }

        
    }
    
    
    /**
     *  Returns an array with the contents of the files table. If no folders exist,
     *  this function makes sure that at least the 'root' folder is specified.
     *  @return An array of array entries of the form [id,name,description]
     */
    function getAllFolders()
    {
        // Make sure the root folder exists
        $this->verifyRootFolder();        
        
        // Query
        $sql = 'SELECT id,name,description FROM #__Musashi_Folder';

        $this->_db->setQuery( $sql );
        $result = $this->_db->loadRowList();
      
        return $result;
      
    }
    
    /**
     *  Returns folder data by name
     *  @param a_sFolderName
     *  @return an object with parameters: id,description
     */
    function getFolderData( $a_sFolderName )
    {
        $sql = 'SELECT id,name FROM #__Musashi_Folder WHERE name="'.$a_sFolderName.'"';
        $this->_db->setQuery($sql);
        return $this->_db->loadObject();
    }
    
    /**
     *  Returns an array with information about all files related to a given directory
     *  @param a_folderID The id of the requested folder
     *  @return An array of entries of the form [id, name, description]
     */
    function getFilesForFolder( $a_folderID )
    {
        $sql = 'SELECT id, name, description '.
               'FROM #__Musashi_File '.
               'WHERE folder ='.$a_folderID;
               
        $this->_db->setQuery($sql);
        return $this->_db->loadRowList();
    }
    
    
    /**
     *  Registers information for a new file.
     *  Returns the ID assigned to the new file
     *  @param a_folderID   ID of the folder where this file is being placed
     *  @param a_name       filename
     *  @param a_desc       description
     *  @return             A storage name for this file, or None if the result fails
     */
    function registerFile( $a_folderID, $a_name, $a_desc )
    {
        $a_name = basename($a_name);
        $sql = 'INSERT INTO #__Musashi_File (folder,name,description) '.
                'VALUES '.sprintf('(%d,"%s","%s")',$a_folderID, $a_name, $a_desc);
                   
        $this->_db->setQuery( $sql );
        if ($this->_db->query())
        {
            // Get the last id
            return mysql_insert_id();            
        }
        
        return false;
        
    }
    
    /**
     *  Creates the physical filename from the provided IDs. This function
     *  encapsulates the file naming algorithm, so we can change it without
     *  having to modify other functions
     *  @param a_fileID     The ID of the file itself
     *  @param a_folderID   Folder where the file is kept
     *  @param a_fullPath   If true, prepend the full path to the file
     */
    function makePhysicalFilename( $a_fileID, $a_folderID, $a_fullPath = false )
    {
        $filename = "";
        if( $a_fullPath ){
            $filename = $this->getUploadPath();
        }
        
        return $filename.$a_fileID.'_'.$a_folderID.'.mfr';
    }
    
    /**
     *  Given the provided file id, returns the complete file path. Useful for
     *  disk operations over a database-stored file
     */
    function getPhysicalFilename( $a_fileID )
    {
        // Database info: Get folder ID
        $sql = 'SELECT folder FROM #__Musashi_File WHERE id='.$a_fileID;
        $this->_db->setQuery($sql);
        $folderID = $this->_db->loadResult();
        
        if( $folderID )
        {
            return $this->makePhysicalFilename($a_fileID,$folderID,true);
        }
        
        return null;
    }
    
    /**
     *  Returns the database name of the provided file
     */
    function getDatabaseName( $a_fileID )
    {
        $sql = 'SELECT name FROM #__Musashi_File WHERE id='.$a_fileID;
        $this->_db->setQuery($sql);
        return $this->_db->loadResult();        
    }

    /**
     *  Deletes the file referenced by the provided id, both in the database, and
     *  physically in the server.
     *  @param a_fileID id for the file     
     */
    function deleteFile( $a_fileID )
    {
        // Get the name to this file
        $fileName = $this->getPhysicalFilename($a_fileID);
        
        // Delete from disk if we have it
        if( $fileName )
        {
            unlink($fileName);
        }
        
        // Remove the database entry
        $sql = 'DELETE FROM #__Musashi_File WHERE id='.$a_fileID;
        $this->_db->setQuery($sql);
        $this->_db->query();                        
        
        return true;
    }
    
    /**
     *  Attempts to delete the selected folder.
     *  This only works if the folder itself is empty
     *  @param a_folderID   Id of the folder to erase
     */
    function deleteFolder( $a_folderID )
    {
        // See if we can get a list of files stored under this folder
        $sql = 'SELECT name FROM #__Musashi_File WHERE folder='.$a_folderID;
        $this->_db->setQuery($sql);
        $result = $this->_db->loadResultArray();
        
        if (count($result) == 0)
        {
            // Proceed to delete the folder itself
            $sql = 'DELETE from #__Musashi_Folder WHERE id='.$a_folderID;
            $this->_db->setQuery($sql);
            $this->_db->query();                
            return true;            
        }
        
        // Something went wrong. Likely, the folder wasn't empty
        return false;
        
        
    }
    
    /**
     *  Helper function that allows you to download a given file without revealing
     *  its location. Receives the file ID
     */
    function downloadFile( $a_fileID )
    {
        // Database info: Get real name and folder id
        $fileName = $this->getPhysicalFilename($a_fileID);
        $dbName   = $this->getDatabaseName( $a_fileID );
        
        // Make sure the file exists, and its readable
        if( $fileName && is_readable($fileName) )
        {
            @ob_end_clean();
            
            // Required by ie
            if( ini_get('zlib.output_compression') ){
               ini_set('zlib.output_compression','off');
            }
            
            header("Content-Type: application/octet-stream");
            header('Content-Disposition: attachment; filename='.basename($dbName));
            header('Content-Transfer-Encoding: binary');
            header('Accept-Ranges: bytes');
            
            // Make it non-cacheable
            header("Cache-Control: private");
            header('Pragma: private');
            header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
            
            // Multipart download and download resuming support
            $fileSize = filesize($fileName);
         
            if( isset($_SERVER['HTTP_RANGE']))
            {
               list($a, $range)         = explode("=",$_SERVER['HTTP_RANGE'],2);
               list($range)             = explode(",",$range,2);
               list($range, $range_end) = explode("-", $range);
               $range = intval($range);
               
               if(!$range_end) {
                  $range_end = $fileSize - 1;
               } else {
                  $range_end = intval($range_end);
               }
 
               $new_length = $range_end - $range + 1;
               header("HTTP/1.1 206 Partial Content");
               header("Content-Length: $new_length");
               header("Content-Range: bytes $range-$range_end / $fileSize");
               
            }
            else
            {
               $new_length = $fileSize;
               header("Content-Length: ".$fileSize);               
            }
            
            // Size of each chunk of data. This should probably change to a fixed value.
            $chunkSize = (1024 * 1024);
            $bytesSent = 0;
            
            if( $file = fopen($fileName, 'r') )
            {
               if( isset($_SERVER['HTTP_RANGE'] )) {
                  fseek($file, $range);
               }
               
               while ( !feof($file)            &&
                       !connection_aborted()   &&
                       ($bytesSent < $new_length)  )
               {
                  $buffer = fread( $file, $chunkSize );
                  print( $buffer );
                  flush();
                  $bytesSent += strlen( $buffer );

               }
               
               // Done
               fclose( $file );
            }
            
           /*
            header('Pragma: public');
            header('Expires: 0');
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header("Cache-Control: public");
            header("Content-Description: File Transfer");
            header("Content-Type: application/octet-stream");
            header('Content-Disposition: attachment; filename='.basename($dbName));
            header('Content-Transfer-Encoding: binary');
            header('Content-Length: ' . filesize($fileName));
            ob_clean();
            flush();
            //ob_end_flush();
            @readfile($fileName);
           */
               
         }
            
      }
    
 }
?>
